package com.woniuxy.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.woniuxy.entity.AllShop;
import com.woniuxy.entity.DTO.algorithm.ProductSalesPredictionDTO;
import com.woniuxy.entity.Order;
import com.woniuxy.mapper.OrderMapper;
import com.woniuxy.mapper.ShopMapper;
import com.woniuxy.service.IProductSalesPredictionService;
import com.woniuxy.util.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author 陈华庆
 * @Date 2025/7/23 2:24
 * @Description TODO
 * 商品销售预测服务实现类
 * 使用时间序列平滑法预测销量，基于过去12周的历史订单数据
 */
@Service
public class ProductSalesPredictionServiceImpl implements IProductSalesPredictionService {

    @Autowired
    private ShopMapper shopMapper;

    @Autowired
    private OrderMapper orderMapper;

    // 预测未来周数
    private static final int PREDICT_WEEKS = 4;
    // 历史数据周数
    private static final int HISTORY_WEEKS = 12;
    // 低库存预警阈值（销量预测的1.5倍）
    private static final double LOW_STOCK_THRESHOLD = 1.5;
    // 缺货预警阈值（销量预测的0.5倍）
    private static final double OUT_OF_STOCK_THRESHOLD = 0.5;
    // 自定义响应状态码 - 商品不存在
    // 为什么使用404：遵循HTTP标准状态码语义，便于前后端理解
    private static final int NOT_FOUND_CODE = 404;
    // 自定义响应状态码 - 系统错误
    private static final int SYSTEM_ERROR_CODE = 500;

    /**
     * 预测指定商品未来4周的销量
     * @param shopId 商品ID
     * @return 预测结果列表
     */
    @Override
    public List<ProductSalesPredictionDTO> predictSalesByShopId(Integer shopId) {
        // 获取商品信息
        AllShop shop = shopMapper.selectById(shopId);

        // 检查商品是否存在，不存在则抛出运行时异常
        // 为什么使用RuntimeException：
        // 1. 避免引入自定义异常类依赖，简化项目结构
        // 2. 保持业务逻辑清晰，同时能够被上层控制器捕获
        // 3. 异常消息中包含明确的错误原因和商品ID，便于调试
        if (shop == null) {
            throw new RuntimeException("商品不存在: " + shopId);
        }

        // 获取过去12周的历史订单数据
        List<Map<String, Object>> historySales = getHistorySalesData(shopId);

        // 预测未来4周销量
        return predictFutureSales(shop, historySales);
    }

    /**
     * 批量预测所有商品未来4周的销量并生成库存预警
     * @param pageNum 当前页码
     * @param pageSize 每页数量
     * @return 分页的预测结果列表
     */
    @Override
    public PageResult<ProductSalesPredictionDTO> batchPredictSalesAndStockWarning(Integer pageNum, Integer pageSize) {
        // 创建分页对象
        Page<AllShop> page = new Page<>(pageNum, pageSize);
        IPage<AllShop> shopPage = shopMapper.selectPage(page, new QueryWrapper<AllShop>().eq("status", 1));

        // 批量预测每个商品的销量
        List<ProductSalesPredictionDTO> predictionList = shopPage.getRecords().stream()
                .flatMap(shop -> {
                    List<Map<String, Object>> historySales = getHistorySalesData(shop.getId());
                    return predictFutureSales(shop, historySales).stream();
                })
                .collect(Collectors.toList());

        // 封装分页结果
        PageResult<ProductSalesPredictionDTO> pageResult = new PageResult<>();
        pageResult.setPageNum(pageNum);
        pageResult.setPageSize(pageSize);
        pageResult.setTotal(shopPage.getTotal() * PREDICT_WEEKS);
        pageResult.setPages((int) Math.ceil((double) pageResult.getTotal() / pageSize));
        pageResult.setList(predictionList);

        return pageResult;
    }

    /**
     * 获取过去12周的历史销售数据
     */
    private List<Map<String, Object>> getHistorySalesData(Integer shopId) {
        // 计算12周前的日期
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.WEEK_OF_YEAR, -HISTORY_WEEKS);
        Date historyStartDate = calendar.getTime();

        // 查询该商品过去12周的订单数据，按周分组统计
        // 为什么使用MIN(create_time)而非直接使用create_time：
        // 1. 解决sql_mode=only_full_group_by问题，确保SELECT中的所有列都在GROUP BY中或使用聚合函数
        // 2. MIN(create_time)可以获取每周的最早订单时间作为该周的起始时间
        // 3. 保持与原逻辑中"week_start"概念一致，但使用聚合函数符合SQL规范
        return orderMapper.selectMaps(new QueryWrapper<Order>()
                .select("MIN(create_time) as week_start", "COUNT(*) as sales")
                .eq("article_id", shopId.toString())
                .ge("create_time", historyStartDate)
                .eq("status", 2)
                .groupBy("YEARWEEK(create_time)")
                .orderByAsc("YEARWEEK(create_time)"));
    }

    /**
     * 预测未来销量并生成库存预警
     */
    private List<ProductSalesPredictionDTO> predictFutureSales(AllShop shop, List<Map<String, Object>> historySales) {
        List<ProductSalesPredictionDTO> predictions = new ArrayList<>();

        // 计算历史平均销量和标准差
        double[] stats = calculateSalesStats(historySales);
        double avgSales = stats[0];
        double stdDev = stats[1];
        int predictedSales = (int) Math.round(avgSales);

        // 生成未来4周的预测数据
        Calendar calendar = Calendar.getInstance();
        for (int i = 1; i <= PREDICT_WEEKS; i++) {
            calendar.add(Calendar.WEEK_OF_YEAR, 1);
            Date predictDate = calendar.getTime();

            // 创建预测DTO
            ProductSalesPredictionDTO prediction = new ProductSalesPredictionDTO();
            prediction.setShopId(shop.getId());
            prediction.setTitle(shop.getTitle());
            prediction.setPredictDate(predictDate);
            prediction.setPrice(shop.getPrice());

            // 设置预测销量和置信区间
            prediction.setPredictedSales(predictedSales);
            prediction.setLowerBound(Math.max(0, (int) Math.round(predictedSales - stdDev)));
            prediction.setUpperBound((int) Math.round(predictedSales + stdDev));

            // 库存预警（注意：实际项目中需要库存数据字段，这里假设库存为销量的2倍用于演示）
            int assumedStock = (int) (predictedSales * 2); // 假设库存
            setStockWarning(prediction, predictedSales, assumedStock);

            predictions.add(prediction);
        }

        return predictions;
    }

    /**
     * 计算历史销量的平均值和标准差
     */
    private double[] calculateSalesStats(List<Map<String, Object>> historySales) {
        if (historySales.isEmpty()) {
            // 为什么使用默认值5和2：
            // 1. 当没有历史数据时提供合理的默认预测值
            // 2. 5单/周是基于校园场景的经验值，标准差2表示合理波动范围
            return new double[]{5, 2}; // 默认值：平均5单/周，标准差2
        }

        List<Integer> salesList = historySales.stream()
                .map(item -> Integer.parseInt(item.get("sales").toString()))
                .collect(Collectors.toList());

        // 计算平均值
        double avg = salesList.stream().mapToInt(Integer::intValue).average().orElse(5);

        // 计算标准差
        double variance = salesList.stream()
                .mapToDouble(s -> Math.pow(s - avg, 2))
                .average()
                .orElse(4);
        double stdDev = Math.sqrt(variance);

        return new double[]{avg, stdDev};
    }

    /**
     * 设置库存预警状态
     */
    private void setStockWarning(ProductSalesPredictionDTO prediction, int predictedSales, int currentStock) {
        // 计算库存可支撑销售的周数
        double stockWeeks = currentStock / (double) predictedSales;

        if (stockWeeks <= OUT_OF_STOCK_THRESHOLD) {
            // 缺货预警
            prediction.setStockWarningStatus(2);
            prediction.setWarningDescription("库存不足，仅可支撑" + String.format("%.1f", stockWeeks) + "周销售");
        } else if (stockWeeks <= LOW_STOCK_THRESHOLD) {
            // 低库存预警
            prediction.setStockWarningStatus(1);
            prediction.setWarningDescription("库存偏低，可支撑" + String.format("%.1f", stockWeeks) + "周销售");
        } else {
            // 库存正常
            prediction.setStockWarningStatus(0);
            prediction.setWarningDescription("库存充足，可支撑" + String.format("%.1f", stockWeeks) + "周销售");
        }
    }
}